package kotan.model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import kotan.datastore.api.Entity;
import kotan.datastore.api.Key;
import kotan.util.KeyFactory;

/**
 * KindModel.
 * @author shuji.w6e
 * @since 1.0
 */
public class KindModel {
    /** logger */
    private static Logger logger = Logger.getLogger(KindModel.class.getName());
    /** Name of this kind */
    private final String kind;
    /** Map of EntityModel */
    final NavigableMap<Key, EntityModel> entities = new ConcurrentSkipListMap<Key, EntityModel>();
    final List<Key> keys = Collections.synchronizedList(new ArrayList<Key>());
    final List<String> propertyNames = Collections.synchronizedList(new ArrayList<String>());
    final Map<String, Class<?>> propertyTypes = Collections.synchronizedMap(new HashMap<String, Class<?>>());
    final ModelListenerHelper helper = new ModelListenerHelper();
    final SortedSet<Long> ids = Collections.synchronizedSortedSet(new TreeSet<Long>());
    final SortedSet<String> names = Collections.synchronizedSortedSet(new TreeSet<String>());

    /**
     * Constructor.
     * @param kind
     * @param list
     */
    public KindModel(String kind, List<Entity> list) {
        this.kind = kind;
        propertyNames.add("key");
        for (Entity entity : list) {
            add(new EntityModel(entity), true);
        }
    }

    /**
     * Add an entity to this model.
     * @param newModel
     */
    public void add(EntityModel newModel) {
        add(newModel, false);
    }

    private void add(EntityModel newModel, boolean ignoreNotify) {
        if (logger.isLoggable(Level.FINE)) logger.fine("add:" + newModel);
        entities.put(newModel.getKey(), newModel);
        add(newModel.getKey());
        for (Entry<String, Object> property : newModel.entity.getProperties().entrySet()) {
            String key = property.getKey();
            if (!propertyNames.contains(key)) propertyNames.add(key);
            // TODO different type for same property
            if (!propertyTypes.containsKey(key) && property.getValue() != null) {
                propertyTypes.put(key, property.getValue().getClass());
            }
        }
        if (ignoreNotify) return;
        helper.notifyListeners();
    }

    /**
     * Return kind name.
     * @return
     */
    public String getName() {
        return kind;
    }

    /**
     * Return ModelListenerHelper.
     * @return helper
     */
    public ModelListenerHelper getHelper() {
        return helper;
    }

    public int propertiesSize() {
        return propertyNames.size();
    }

    public Iterable<String> propertyNames() {
        return propertyNames;
    }

    /**
     * 
     * @param entityModel
     * @param column
     * @param newValue
     */
    public void setValue(EntityModel entityModel, int column, Object newValue) {
        String propertyName = propertyNames.get(column);
        entityModel.entity.setProperty(propertyName, newValue);
        this.helper.notifyListeners();
    }

    public Object getValue(int index, int column) {
        if (column == 0) return get(index).getKey();
        return get(index).entity.getProperty(getPropertyName(column));
    }

    /**
     * Return size of entities in this kind.
     * @return
     */
    public int size() {
        return entities.size();
    }

    public EntityModel get(int index) {
        return entities.get(keys.get(index));
    }

    public String getPropertyName(int column) {
        return propertyNames.get(column);
    }

    public boolean isKeyColumn(int column) {
        if (column == 0) return true;
        return false;
    }

    public Class<?> getPropertyType(int index, int column) {
        if (column == 0) return Key.class; // Key
        Object value = getValue(index, column);
        if (value == null) return null;
        return value.getClass();
    }
    
    public Class<?> getPropertyType(int column) {
        String propName = getPropertyName(column);
        return propertyTypes.get(propName);
    }

    /**
     * Return an entity
     * @param key
     * @return
     */
    public EntityModel get(Key key) {
        return entities.get(key);
    }

    /**
     * Remove an entity from this model.
     * @param key
     */
    public void remove(Key key) {
        entities.remove(key);
        keys.remove(key);
        helper.notifyListeners();
    }

    /**
     * Create a valid new key
     * @return
     */
    public Key allocateKey() {
        long id = ids.size() + 1;
        while (ids.contains(id))
            id++;
        return KeyFactory.createKey(kind, id);
    }

    private void add(Key key) {
        keys.add(key);
        if (0 < key.getId()) {
            ids.add(key.getId());
        } else {
            names.add(key.getName());
        }
    }

    public boolean contains(Key key) {
        return keys.contains(key);
    }

    @Override
    public String toString() {
        StringBuilder str = new StringBuilder();
        str.append("KindModel[").append(kind).append(", size=").append(entities.size()).append("]");
        return str.toString();
    }


}
